package org.fujay.langchain4jserver.infrastructure.storage; // 新建包

import cn.hutool.core.util.StrUtil;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.fujay.langchain4jserver.domain.knowledge.entity.File;
import org.fujay.langchain4jserver.domain.knowledge.vo.StorageStrategy;
import org.fujay.langchain4jserver.domain.shared.exception.FileStorageException;
import org.fujay.langchain4jserver.domain.shared.service.StorageProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.*;
import java.util.Optional;

@Slf4j
@Service
public class LocalStorageProvider implements StorageProvider {

    @Value("${app.storage.local.base-path:/tmp/langchain4j-server-files}") // 从配置注入，提供默认值
    private String basePath;

    private Path baseDirectory;

    @PostConstruct
    public void init() {
        try {
            baseDirectory = Paths.get(basePath);
            if (!Files.exists(baseDirectory)) {
                Files.createDirectories(baseDirectory);
                log.info("Created local storage base directory: {}", baseDirectory.toAbsolutePath());
            } else {
                log.info("Using existing local storage base directory: {}", baseDirectory.toAbsolutePath());
            }
        } catch (IOException e) {
            log.error("Failed to initialize local storage directory: {}", basePath, e);
            throw new RuntimeException("Failed to initialize local storage", e);
        }
    }

    /**
     * 将输入流存储到指定的路径
     *
     * @param inputStream 要存储的输入流
     * @param filePath    建议的存储路径
     * @param strategy    存储策略
     * @return 实际存储的文件路径
     * @throws FileStorageException 存储失败时抛出
     */
    @Override
    public String store(InputStream inputStream, String filePath, StorageStrategy strategy)
            throws FileStorageException {
        Assert.notNull(inputStream, "输入流不能为null.");
        Assert.hasText(filePath, "文件路径不能为空.");
        if (strategy != StorageStrategy.LOCAL) {
            log.warn("文件存储类型必须是Local: {}", strategy);
            throw new IllegalArgumentException(
                    "仅支持Local类型: " + strategy);
        }

        Path targetPath = resolvePath(filePath);

        try (InputStream autoClosableStream = inputStream) {
            // 确保父目录存在
            Path parentDir = targetPath.getParent();
            if (parentDir != null) { // 根目录可能没有父目录
                if (!Files.exists(parentDir)) {
                    Files.createDirectories(parentDir);
                    log.info("Created parent directory for file: {}", parentDir.toAbsolutePath());
                }
            }

            Files.copy(autoClosableStream, targetPath, StandardCopyOption.REPLACE_EXISTING);
            log.info("文件保存成功: {}", targetPath.toAbsolutePath());

            return filePath;

        } catch (IOException e) {
            log.error("文件上传失败: {},文件目标路径 {}.", filePath, targetPath.toAbsolutePath(), e);
            throw new FileStorageException("文件上传失败: '" + filePath, e);
        } catch (SecurityException e) {
            log.error("Security violation while attempting to store file {}: {}", filePath, e.getMessage());
            throw new FileStorageException("Security violation for file path: " + filePath, e);
        }
    }

    @Override
    public InputStream getFileStream(File file) throws IOException {
        validateFile(file, StorageStrategy.LOCAL);
        Path filePath = resolvePath(file.getFilePath());
        log.debug("Getting InputStream for local file: {}", filePath);
        return Files.newInputStream(filePath, StandardOpenOption.READ);
    }

    @Override
    public OutputStream getOutputStream(String relativePath, StorageStrategy strategy) {
        if (strategy != StorageStrategy.LOCAL) {
            throw new IllegalArgumentException(
                    "LocalStorageProvider only supports LOCAL strategy for getOutputStream");
        }
        Assert.hasText(relativePath, "Relative path cannot be empty for getOutputStream");
        Path filePath = resolvePath(relativePath);

        try {
            // 确保父目录存在
            Files.createDirectories(filePath.getParent());
            log.debug("Getting OutputStream for local file: {}", filePath);
            return Files.newOutputStream(
                    filePath,
                    StandardOpenOption.CREATE,
                    StandardOpenOption.WRITE,
                    StandardOpenOption.TRUNCATE_EXISTING);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void deleteFile(File file) {
        validateFile(file, StorageStrategy.LOCAL);
        Path filePath = resolvePath(file.getFilePath());
        try {
            Files.deleteIfExists(filePath);
            log.info("Deleted local file: {}", filePath);
        } catch (NoSuchFileException e) {
            log.warn("Attempted to delete non-existent local file: {}", filePath);
        } catch (DirectoryNotEmptyException e) {
            log.error("Failed to delete non-empty directory: {}", filePath, e);
        } catch (IOException e) {
            log.error("Failed to delete local file: {}", filePath, e);
        }
    }

    @Override
    public Optional<String> getFileUrl(File file, Integer expirationSeconds) {
        // 本地文件系统通常不直接提供带有效期的外部访问 URL
        validateFile(file, StorageStrategy.LOCAL);
        Path filePath = resolvePath(file.getFilePath());
        // 可以返回文件 URI，但这通常只在本地或特定网络环境可用
        // return Optional.of(filePath.toUri().toString());
        log.warn("Generating external URL for local file {} is not directly supported.", filePath);
        return Optional.empty();
    }

    @Override
    public boolean fileExists(File file) {
        if (file == null
                || file.getStorageStrategy() != StorageStrategy.LOCAL
                || StrUtil.isEmpty(file.getFilePath())) {
            return false;
        }
        Path filePath = resolvePath(file.getFilePath());
        return Files.exists(filePath);
    }

    // --- 辅助方法 ---

    private void validateFile(File file, StorageStrategy expectedStrategy) {
        Assert.notNull(file, "File object cannot be null");
        Assert.notNull(file.getStorageStrategy(), "File storage strategy cannot be null");
        Assert.hasText(file.getFilePath(), "File path cannot be empty");
        if (file.getStorageStrategy() != expectedStrategy) {
            throw new IllegalArgumentException(
                    "Unsupported storage strategy for this provider. Expected: "
                            + expectedStrategy
                            + ", Found: "
                            + file.getStorageStrategy());
        }
    }

    // 将相对路径解析为绝对路径
    private Path resolvePath(String relativePath) {
        Assert.hasText(relativePath, "Relative path cannot be empty");
        // 规范化路径，防止路径遍历攻击 (e.g., "../../../etc/passwd")
        Path resolved = baseDirectory.resolve(relativePath).normalize();
        // 确保解析后的路径仍然在 baseDirectory 下
        if (!resolved.startsWith(baseDirectory)) {
            throw new SecurityException(
                    "Invalid relative path resolves outside base directory: " + relativePath);
        }
        return resolved;
    }

}
